Skip to content

Fix saltclass literal '^' list-override marker for single class#69759

Open
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-50755
Open

Fix saltclass literal '^' list-override marker for single class#69759
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-50755

Conversation

@ggiesen

@ggiesen ggiesen commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes saltclass leaving a literal ^ element in the merged pillar when a list override is defined by a single class and no existing list is present to override.

In salt/utils/saltclass.py, dict_merge only stripped the leading ^ override marker inside the key-present list+list branch. When the key was absent from the target, control fell to else: a[key] = b[key], which plain-assigned the value verbatim. In real usage the override list is nested (pillars dict -> pkgs list), so the absent key is actually the pillars dict; the whole subtree was assigned by reference and the code never descended to the list, so the ^ survived.

The key-absent branch now descends into dicts (processing nested override lists at any depth) and honours a leading ^ on a list. An empty-list guard avoids an IndexError, and the marker is stripped with non-mutating slicing.

What issues does this PR fix or reference?

Fixes #50755

Previous Behavior

A class defining only an override list, for example:

pillars:
  pkgs:
    - ^
    - three

produced pkgs == ['^', 'three'], so the literal ^ leaked into state runs (e.g. The following packages would be installed/updated: ^).

New Behavior

The same class produces pkgs == ['three']; the ^ marker is stripped whether or not a prior list exists to override. Values with no ^ marker are unchanged.

Merge requirements satisfied?

  • Docs: N/A (no user-facing docs change; behaviour now matches documented override semantics)
  • Changelog entry (changelog/50755.fixed.md)
  • Tests written and passing

Tests added in tests/pytests/unit/utils/test_saltclass.py: a direct-altitude test using the production-exact nested shape from expanded_dict_from_minion, a deeper-nesting variant, an inverse test proving the existing key-present override path is unchanged, a marker-free key-absent test, an empty-list guard test, and a plain-extend test.

Commits signed with GPG?: No

The '^' list-override marker was only stripped when the target dict
already contained a matching list to override. When a class defined an
override list with no prior list present, dict_merge plain-assigned the
value (including the nested "pillars" dict) by reference and never
descended to the list, leaving a literal '^' element in the merged
pillar. Descend into dicts and honour a leading '^' on a list in the
key-absent branch as well.

Fixes saltstack#50755
@ggiesen
ggiesen requested a review from a team as a code owner July 10, 2026 17:54
@charzl charzl self-assigned this Jul 10, 2026
@dwoz dwoz added the test:full Run the full test suite label Jul 10, 2026
@twangboy twangboy added this to the Sulphur v3006.28 milestone Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants